import { AudioProcessing, SampleBitsTypes } from '@/libs/recorder'

export interface AudioBufferToWavParams {
  numberOfChannels: LooseObject
  sampleRate: LooseObject
  getChannelData: (arg0: number) => LooseObject
}

export default function audioBufferToWav(
  buffer: AudioBufferToWavParams,
  sampleBits: SampleBitsTypes | number = SampleBitsTypes.Bit16,
  numChannels = 1,
  float32?: boolean,
): AudioProcessing {
  const sampleRate = buffer.sampleRate
  const format = float32 ? 3 : 1

  const result =
    numChannels === 2 ? interleave(buffer.getChannelData(0), buffer.getChannelData(1)) : buffer.getChannelData(0)
  const peak = Math.max.apply(
    null,
    result.map((x) => x),
  )
  return {
    buffer: encodeWAV(result, format, sampleRate, numChannels, sampleBits || SampleBitsTypes.Bit16),
    peak,
  }
}

export function encodeWAV(
  samples: string | LooseObject[],
  format: number,
  sampleRate: number,
  numChannels: number,
  sampleBits: number,
) {
  const buffer = new ArrayBuffer((samples.length * sampleBits) / 8)
  const view = new DataView(buffer)
  if (format === 1) {
    // Raw PCM
    floatTo16BitPCM(view, 0, samples)
  } else {
    writeFloat32(view, 0, samples)
  }
  return new Blob([buffer])
}

export function interleave(inputL: string | LooseObject[], inputR: string | LooseObject[]) {
  const length = inputL.length + inputR.length
  const result = new Float32Array(length)

  let index = 0
  let inputIndex = 0

  while (index < length) {
    result[index++] = inputL[inputIndex]
    result[index++] = inputR[inputIndex]
    inputIndex++
  }
  return result
}

export function writeFloat32(output: DataView, offset: number, input: string | LooseObject[]) {
  for (let i = 0; i < input.length; i++, offset += 4) {
    output.setFloat32(offset, input[i], true)
  }
}

export function floatTo16BitPCM(output: DataView, offset: number, input: string | LooseObject[]) {
  for (let i = 0; i < input.length; i++, offset += 2) {
    const s = Math.max(-1, Math.min(1, input[i]))
    output.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
  }
}

export function writeString(view: DataView, offset: number, string: string) {
  for (let i = 0; i < string.length; i++) {
    view.setUint8(offset + i, string.charCodeAt(i))
  }
}

export function isAudioDB(buffer: LooseObject, peak: number) {
  const int16Audio = new Int16Array(buffer)
  if (int16Audio.length) {
    let avgEnergy = 0
    int16Audio.forEach((fragment) => {
      avgEnergy += fragment * fragment
    })

    avgEnergy = Math.sqrt(avgEnergy / (int16Audio.length || 1))
    const db = (20 * Math.log10(avgEnergy / 32767)).toFixed(3)
    console.info('-'.repeat(10), db)
  }
  return peak * 100 <= 1
}
